#pragma once
#ifndef CLIB_ETH_H
#define CLIB_ETH_H



#include "clib_type.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <netinet/ether.h>

// sizeof(struct ethhdr)
#define ETH_HDR_LEN                     14

// sizeof(struct iphdr)
#define IP4_HDR_LEN                     20

// sizeof(struct tcphdr)
#define TCP_HDR_LEN                     20

#define ETH_ADDR_LEN                    6

#define ETH_ADDR_STR_LEN                17

/**< Locally assigned Eth. address. */
#define ETHER_LOCAL_ADMIN_ADDR          0x02

/**< Multicast or broadcast Eth. address. */
#define ETHER_GROUP_ADDR                0x01

#ifdef __cplusplus
extern "C" {
#endif

typedef struct eth_addr_s   eth_addr_t;

typedef struct eth_hdr_s    eth_hdr_t;

struct eth_addr_s {
    u08_t bytes[ETH_ADDR_LEN];
} __attribute__((__aligned__(2)));

struct eth_hdr_s {
    eth_addr_t  dst_addr;
    eth_addr_t  src_addr;
    u16_t       type;
} __attribute__((__aligned__(2)));

#define ETHER_ADDR_FMTS         "%02X:%02X:%02X:%02X:%02X:%02X"

#define ETHER_ADDR_BYTES(macs)  ((macs)->bytes[0]), \
					            ((macs)->bytes[1]), \
					            ((macs)->bytes[2]), \
					            ((macs)->bytes[3]), \
					            ((macs)->bytes[4]), \
					            ((macs)->bytes[5])

static inline i32_t 
clib_ether_addr_is_zero(const eth_addr_t *ea)
{
    const u16_t *word = (const u16_t *)ea;

    return (word[0] | word[1] | word[2]) == 0;
}

static inline i32_t
clib_ether_addr_is_same(const eth_addr_t *ea1, const eth_addr_t *ea2)
{
	const u16_t *w1 = (const u16_t *)ea1;
	const u16_t *w2 = (const u16_t *)ea2;

	return ((w1[0] ^ w2[0]) | (w1[1] ^ w2[1]) | (w1[2] ^ w2[2])) == 0;
}

static inline void 
clib_ether_addr_copy(eth_addr_t *dst, const eth_addr_t *src)
{
    *dst = *src;
}

static inline i32_t 
clib_ether_addr_is_unicast(const eth_addr_t *ea)
{
	return (ea->bytes[0] & ETHER_GROUP_ADDR) == 0;
}

static inline i32_t
clib_ether_addr_is_multicast(const eth_addr_t *ea)
{
	return ea->bytes[0] & ETHER_GROUP_ADDR;
}

static inline i32_t 
clib_ether_addr_is_broadcast(const eth_addr_t *ea)
{
	const u16_t *word = (const u16_t *)ea;

	return (word[0] & word[1] & word[2]) == 0xFFFF;
}

static inline i32_t
clib_ether_addr_is_universal(const eth_addr_t *ea)
{
	return (ea->bytes[0] & ETHER_LOCAL_ADMIN_ADDR) == 0;
}

static inline i32_t
clib_admin_ether_addr_is_local(const eth_addr_t *ea)
{
	return (ea->bytes[0] & ETHER_LOCAL_ADMIN_ADDR) != 0;
}

static inline i32_t
clib_assigned_ether_addr_is_valid(const eth_addr_t *ea)
{
	return clib_ether_addr_is_unicast(ea) && (!clib_ether_addr_is_zero(ea));
}

static inline void
clib_ether_addr_format(char *fmt, u16_t size,const eth_addr_t *ea)
{
	snprintf(fmt, size, ETHER_ADDR_FMTS, ETHER_ADDR_BYTES(ea));
}


static inline void 
clib_ether_addr_swap(eth_hdr_t *eth)
{
    eth_addr_t addr;
    clib_ether_addr_copy(&addr, &eth->dst_addr);
    clib_ether_addr_copy(&eth->dst_addr, &eth->src_addr);
    clib_ether_addr_copy(&eth->src_addr, &addr);
}

static inline void 
clib_ether_hdr_set(eth_hdr_t *eth, u16_t type, const eth_addr_t *dst_addr, const eth_addr_t *src_addr)
{
    eth->type = htons(type);
    clib_ether_addr_copy(&eth->dst_addr, dst_addr);
    clib_ether_addr_copy(&eth->src_addr, src_addr);
}

static inline void
clib_ether_addr_random(eth_addr_t *addr) 
{
    union {

        u64_t rand;

        struct {
            u32_t r1;
            u32_t r2;
        };
    } st;

    struct timeval ts;

    gettimeofday(&ts, NULL);

    srand(ts.tv_usec);

    st.r1 = rand();

    srand(ts.tv_usec + 7);

    st.r2 = rand();

    memcpy(addr, &st.rand, ETH_ADDR_LEN);

    /* clear multicast bit */
    addr->bytes[0] &= (u08_t)~ETHER_GROUP_ADDR;

    /* set local assignment bit */
    addr->bytes[0] |= ETHER_LOCAL_ADMIN_ADDR;  
}

#ifdef __cplusplus
}
#endif

#endif